#include "UnitGroup.h"

#include "..\BWAPI\Player.h"
#include "..\BWTA\Region.h"
#include "..\EnumMapping.h"
#include "..\Util.h"

using namespace System::Runtime::InteropServices;

namespace BroodWar
{
	namespace Addon
	{
		private ref class UnitGroupEnumerator : public IEnumerator<Unit^>
		{
		private:
			std::set<BWAPI::Unit*>* instance;
			std::set<BWAPI::Unit*>::iterator* it;
			bool first;

		public:
			UnitGroupEnumerator(std::set<BWAPI::Unit*>* vc)
			{
				instance = vc;
				it = new std::set<BWAPI::Unit*>::iterator();
				*it = instance->begin();
				first = true;
			}

			~UnitGroupEnumerator() //this is our dispose method
			{
				if(it != NULL)
					delete it;
			}

			virtual property Unit^ Current
			{
				Unit^ get()
				{
					if(it == NULL)
						throw gcnew NullReferenceException("iterator");
					return ConvertUnit(**it);
				}
			}

			virtual void Reset()
			{
				*it = instance->begin();
				first = true;
			}

			virtual bool MoveNext()
			{
				if(first)
					first = false;
				else
					(*it)++;

				if(*it == instance->end())
					return false;
				return true;
			}

		private:
			virtual property Object^ CurrentObject
			{
				virtual Object^ get() sealed = System::Collections::IEnumerator::Current::get
				{
					return this->Current;
				}
			}
		};

		UnitGroup^ ConvertUnitGroup(BWSAL::UnitGroup group)
		{
			return gcnew UnitGroup(group);
		}
		
		UnitGroup::UnitGroup(BWSAL::UnitGroup* group)
		{
			instance = group;
			dispose = false;
		}

		UnitGroup::UnitGroup(BWSAL::UnitGroup group)
		{
			instance = new BWSAL::UnitGroup(group);
			dispose = true;
		}

		UnitGroup::~UnitGroup()
		{
			if(dispose)
				delete instance;
		}

		UnitGroup::!UnitGroup()
		{
			if(dispose)
				delete instance;
		}

		void UnitGroup::Add(Unit^ item)
		{
			instance->insert(ConvertUnit(item));
		}
		
		void UnitGroup::Clear()
		{
			instance->clear();
		}
		
		bool UnitGroup::Contains(Unit^ item)
		{
			return instance->contains(ConvertUnit(item));
		}
				
		void UnitGroup::CopyTo(array<Unit^>^ target, int arrayIndex)
		{
			throw gcnew NotImplementedException();
		}
		
		IEnumerator<Unit^>^ UnitGroup::GetEnumerator()
		{
			return gcnew UnitGroupEnumerator(instance);
		}
		
		bool UnitGroup::Remove(Unit^ item)
		{
			return instance->erase(ConvertUnit(item)) > 0;
		}
		
		int UnitGroup::Count::get()
		{
			return instance->size();
		}
		
		bool UnitGroup::IsReadOnly::get()
		{
			return false;
		}

		System::Collections::IEnumerator^ UnitGroup::GetEnumerator2()
		{
			return GetEnumerator();
		}

		Unit^ UnitGroup::NearestUnit(Position^ position)
		{
			return ConvertUnit(instance->getNearest(ConvertPosition(position)));
		}
		
		Position^ UnitGroup::Center::get()
		{
			return ConvertPosition(instance->getCenter());
		}

		bool UnitGroup::Attack(Position^ target, bool shiftQueueCommand)
		{
			return instance->attack(ConvertPosition(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Attack(Position^ target)
		{
			return instance->attack(ConvertPosition(target));
		}

		bool UnitGroup::Attack(Unit^ target, bool shiftQueueCommand)
		{
			return instance->attack(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Attack(Unit^ target)
		{
			return instance->attack(ConvertUnit(target));
		}

		bool UnitGroup::Build(TilePosition^ target, Api::Enum::UnitType type)
		{
			return instance->build(ConvertTile(target), ConvertUnitType(type));
		}

		bool UnitGroup::BuildAddon(Api::Enum::UnitType type)
		{
			return instance->buildAddon(ConvertUnitType(type));
		}

		bool UnitGroup::Train(Api::Enum::UnitType type)
		{
			return instance->train(ConvertUnitType(type));
		}

		bool UnitGroup::Morph(Api::Enum::UnitType type)
		{
			return instance->morph(ConvertUnitType(type));
		}

		bool UnitGroup::Research(Api::Enum::TechType type)
		{
			return instance->research(ConvertTech(type));
		}

		bool UnitGroup::Upgrade(Api::Enum::UpgradeType upgrade)
		{
			return instance->upgrade(ConvertUpgrade(upgrade));
		}

		bool UnitGroup::SetRallyPoint(Position^ target)
		{
			return instance->setRallyPoint(ConvertPosition(target));
		}

		bool UnitGroup::SetRallyPoint(Unit^ target)
		{
			return instance->setRallyPoint(ConvertUnit(target));
		}

		bool UnitGroup::Move(Position^ target, bool shiftQueueCommand)
		{
			return instance->move(ConvertPosition(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Move(Position^ target)
		{
			return instance->move(ConvertPosition(target));
		}

		bool UnitGroup::Patrol(Position^ target, bool shiftQueueCommand)
		{
			return instance->patrol(ConvertPosition(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Patrol(Position^ target)
		{
			return instance->patrol(ConvertPosition(target));
		}

		bool UnitGroup::HoldPosition(bool shiftQueueCommand)
		{
			return instance->holdPosition(shiftQueueCommand);
		}
		
		bool UnitGroup::HoldPosition()
		{
			return instance->holdPosition();
		}

		bool UnitGroup::Stop(bool shiftQueueCommand)
		{
			return instance->stop(shiftQueueCommand);
		}
		
		bool UnitGroup::Stop()
		{
			return instance->stop();
		}

		bool UnitGroup::Follow(Unit^ target, bool shiftQueueCommand)
		{
			return instance->follow(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Follow(Unit^ target)
		{
			return instance->follow(ConvertUnit(target));
		}

		bool UnitGroup::Gather(Unit^ target, bool shiftQueueCommand)
		{
			return instance->gather(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Gather(Unit^ target)
		{
			return instance->gather(ConvertUnit(target));
		}

		bool UnitGroup::ReturnCargo(bool shiftQueueCommand)
		{
			return instance->returnCargo(shiftQueueCommand);
		}
		
		bool UnitGroup::ReturnCargo()
		{
			return instance->returnCargo();
		}

		bool UnitGroup::Repair(Unit^ target, bool shiftQueueCommand)
		{
			return instance->repair(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Repair(Unit^ target)
		{
			return instance->repair(ConvertUnit(target));
		}

		bool UnitGroup::Burrow()
		{
			return instance->burrow();
		}

		bool UnitGroup::Unburrow()
		{
			return instance->unburrow();
		}

		bool UnitGroup::Cloak()
		{
			return instance->cloak();
		}

		bool UnitGroup::Decloak()
		{
			return instance->decloak();
		}

		bool UnitGroup::Siege()
		{
			return instance->siege();
		}

		bool UnitGroup::Unsiege()
		{
			return instance->unsiege();
		}

		bool UnitGroup::Lift()
		{
			return instance->lift();
		}

		bool UnitGroup::Land(TilePosition^ target)
		{
			return instance->land(ConvertTile(target));
		}

		bool UnitGroup::Load(Unit^ target, bool shiftQueueCommand)
		{
			return instance->load(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::Load(Unit^ target)
		{
			return instance->load(ConvertUnit(target));
		}

		bool UnitGroup::Unload(Unit^ target)
		{
			return instance->unload(ConvertUnit(target));
		}

		bool UnitGroup::UnloadAll(bool shiftQueueCommand)
		{
			return instance->unloadAll(shiftQueueCommand);
		}
		
		bool UnitGroup::UnloadAll()
		{
			return instance->unloadAll();
		}

		bool UnitGroup::UnloadAll(Position^ target, bool shiftQueueCommand)
		{
			return instance->unloadAll(ConvertPosition(target), shiftQueueCommand);
		}
		
		bool UnitGroup::UnloadAll(Position^ target)
		{
			return instance->unloadAll(ConvertPosition(target));
		}

		bool UnitGroup::RightClick(Position^ target, bool shiftQueueCommand)
		{
			return instance->rightClick(ConvertPosition(target), shiftQueueCommand);
		}
		
		bool UnitGroup::RightClick(Position^ target)
		{
			return instance->rightClick(ConvertPosition(target));
		}

		bool UnitGroup::RightClick(Unit^ target, bool shiftQueueCommand)
		{
			return instance->rightClick(ConvertUnit(target), shiftQueueCommand);
		}
		
		bool UnitGroup::RightClick(Unit^ target)
		{
			return instance->rightClick(ConvertUnit(target));
		}

		bool UnitGroup::HaltConstruction()
		{
			return instance->haltConstruction();
		}

		bool UnitGroup::CancelConstruction()
		{
			return instance->cancelConstruction();
		}

		bool UnitGroup::CancelAddon()
		{
			return instance->cancelAddon();
		}

		bool UnitGroup::CancelTrain(int slot)
		{
			return instance->cancelTrain(slot);
		}
		
		bool UnitGroup::CancelTrain()
		{
			return instance->cancelTrain();
		}

		bool UnitGroup::CancelMorph()
		{
			return instance->cancelMorph();
		}

		bool UnitGroup::CancelResearch()
		{
			return instance->cancelResearch();
		}

		bool UnitGroup::CancelUpgrade()
		{
			return instance->cancelUpgrade();
		}

		bool UnitGroup::UseTech(Api::Enum::TechType tech)
		{
			return instance->useTech(ConvertTech(tech));
		}

		bool UnitGroup::UseTech(Api::Enum::TechType tech, Position^ target)
		{
			return instance->useTech(ConvertTech(tech), ConvertPosition(target));
		}

		bool UnitGroup::UseTech(Api::Enum::TechType tech, Unit^ target)
		{
			return instance->useTech(ConvertTech(tech), ConvertUnit(target));
		}

		UnitGroup^ UnitGroup::InRadius(double radius, Position^ position)
		{
			return ConvertUnitGroup(instance->inRadius(radius, ConvertPosition(position)));
		}
		
		UnitGroup^ UnitGroup::InRegion(Terrain::Region^ region)
		{
			return ConvertUnitGroup(instance->inRegion(Terrain::ConvertRegion(region)));
		}
		
		UnitGroup^ UnitGroup::OnlyNearestChokepoint(Chokepoint^ choke)
		{
			return ConvertUnitGroup(instance->onlyNearestChokepoint(ConvertChokepoint(choke)));
		}
		
		UnitGroup^ UnitGroup::OnlyNearestBaseLocation(BaseLocation^ location)
		{
			return ConvertUnitGroup(instance->onlyNearestBaseLocation(ConvertBaseLocation(location)));
		}
		
		UnitGroup^ UnitGroup::OnlyNearestUnwalkablePolygon(Polygon^ polygon)
		{
			return ConvertUnitGroup(instance->onlyNearestUnwalkablePolygon(ConvertPolygon(polygon)));
		}

		UnitGroup^ UnitGroup::Filter(FilterFlag f1)
		{
			return ConvertUnitGroup((*instance)((int)f1));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterFlag f1, FilterFlag f2)
		{
			return ConvertUnitGroup((*instance)((int)f1, (int)f2));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterFlag f1, FilterFlag f2, FilterFlag f3)
		{
			return ConvertUnitGroup((*instance)((int)f1, (int)f2, (int)f3));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterFlag f1, FilterFlag f2, FilterFlag f3, FilterFlag f4)
		{
			return ConvertUnitGroup((*instance)((int)f1, (int)f2, (int)f3, (int)f4));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterFlag f1, FilterFlag f2, FilterFlag f3, FilterFlag f4, FilterFlag f5)
		{
			return ConvertUnitGroup((*instance)((int)f1, (int)f2, (int)f3, (int)f4, (int)f5));
		}
		
		UnitGroup^ UnitGroup::Filter(FliterAttributeScalar a, String^ compare, double value)
		{
			IntPtr str = Marshal::StringToHGlobalAnsi(compare);
			UnitGroup^ g = ConvertUnitGroup((*instance)(static_cast<BWSAL::FliterAttributeScalar>(a), (const char*)str.ToPointer(), value));
			Marshal::FreeHGlobal(str);
			return g;
		}
		
		UnitGroup^ UnitGroup::Filter(FliterAttributeScalar a, String^ compare, int value)
		{
			IntPtr str = Marshal::StringToHGlobalAnsi(compare);
			UnitGroup^ g = ConvertUnitGroup((*instance)(static_cast<BWSAL::FliterAttributeScalar>(a), (const char*)str.ToPointer(), value));
			Marshal::FreeHGlobal(str);
			return g;
		}
		
		UnitGroup^ UnitGroup::Filter(Player^ player)
		{
			return ConvertUnitGroup((*instance)(ConvertPlayer(player)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeUnit a, Unit^ unit)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeUnit>(a), ConvertUnit(unit)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeType a, Api::Enum::UnitType type)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeType>(a), ConvertUnitType(type)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeType a, Api::Enum::TechType type)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeType>(a), ConvertTech(type)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeType a, Api::Enum::UpgradeType type)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeType>(a), ConvertUpgrade(type)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeOrder a, Api::Enum::OrderType type)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeOrder>(a), EnumMapping::OrderType->Native(type)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributePosition a, Position^ position)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributePosition>(a), ConvertPosition(position)));
		}
		
		UnitGroup^ UnitGroup::Filter(FilterAttributeTilePosition a, TilePosition^ position)
		{
			return ConvertUnitGroup((*instance)(static_cast<BWSAL::FilterAttributeTilePosition>(a), ConvertTile(position)));
		}

		UnitGroup^ UnitGroup::FilterNot(FilterFlag f1)
		{
			return ConvertUnitGroup(instance->not((int)f1));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterFlag f1, FilterFlag f2)
		{
			return ConvertUnitGroup(instance->not((int)f1, (int)f2));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterFlag f1, FilterFlag f2, FilterFlag f3)
		{
			return ConvertUnitGroup(instance->not((int)f1, (int)f2, (int)f3));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterFlag f1, FilterFlag f2, FilterFlag f3, FilterFlag f4)
		{
			return ConvertUnitGroup(instance->not((int)f1, (int)f2, (int)f3, (int)f4));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterFlag f1, FilterFlag f2, FilterFlag f3, FilterFlag f4, FilterFlag f5)
		{
			return ConvertUnitGroup(instance->not((int)f1, (int)f2, (int)f3, (int)f4, (int)f5));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FliterAttributeScalar a, String^ compare, double value)
		{
			IntPtr str = Marshal::StringToHGlobalAnsi(compare);
			UnitGroup^ g = ConvertUnitGroup(instance->not(static_cast<BWSAL::FliterAttributeScalar>(a), (const char*)str.ToPointer(), value));
			Marshal::FreeHGlobal(str);
			return g;
		}
		
		UnitGroup^ UnitGroup::FilterNot(FliterAttributeScalar a, String^ compare, int value)
		{
			IntPtr str = Marshal::StringToHGlobalAnsi(compare);
			UnitGroup^ g = ConvertUnitGroup(instance->not(static_cast<BWSAL::FliterAttributeScalar>(a), (const char*)str.ToPointer(), value));
			Marshal::FreeHGlobal(str);
			return g;
		}
		
		UnitGroup^ UnitGroup::FilterNot(Player^ player)
		{
			return ConvertUnitGroup(instance->not(ConvertPlayer(player)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeUnit a, Unit^ unit)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeUnit>(a), ConvertUnit(unit)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeType a, Api::Enum::UnitType type)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeType>(a), ConvertUnitType(type)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeType a, Api::Enum::TechType type)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeType>(a), ConvertTech(type)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeType a, Api::Enum::UpgradeType type)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeType>(a), ConvertUpgrade(type)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeOrder a, Api::Enum::OrderType type)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeOrder>(a), EnumMapping::OrderType->Native(type)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributePosition a, Position^ position)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributePosition>(a), ConvertPosition(position)));
		}
		
		UnitGroup^ UnitGroup::FilterNot(FilterAttributeTilePosition a, TilePosition^ position)
		{
			return ConvertUnitGroup(instance->not(static_cast<BWSAL::FilterAttributeTilePosition>(a), ConvertTile(position)));
		}

		UnitGroup^ UnitGroup::operator + (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr)
				return second;
			if(second == nullptr)
				return first;
			return ConvertUnitGroup((*(first->instance)) + (*(second->instance)));
		}

		UnitGroup^ UnitGroup::operator * (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			return ConvertUnitGroup((*(first->instance)) * (*(second->instance)));
		}
		
		UnitGroup^ UnitGroup::operator ^ (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			return ConvertUnitGroup((*(first->instance)) ^ (*(second->instance)));
		}
		
		UnitGroup^ UnitGroup::operator - (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			return ConvertUnitGroup((*(first->instance)) - (*(second->instance)));
		}
		
		UnitGroup^ UnitGroup::operator += (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr)
				return second;
			if(second == nullptr)
				return first;
			*(first->instance) += *(second->instance);
			return first;
		}
		
		UnitGroup^ UnitGroup::operator *= (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			*(first->instance) *= *(second->instance);
			return first;
		}
		
		UnitGroup^ UnitGroup::operator ^= (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			*(first->instance) ^= *(second->instance);
			return first;
		}
		
		UnitGroup^ UnitGroup::operator -= (UnitGroup^ first, UnitGroup^ second)
		{
			if(first == nullptr || second == nullptr)
				return nullptr;
			*(first->instance) -= *(second->instance);
			return first;
		}

		UnitGroup^ UnitGroupExtension::ToUnitGroup(ICollection<Unit^>^ units)
		{
			std::set<BWAPI::Unit*> unitsgroup = ToSet<Unit^, BWAPI::Unit*>(units, &ConvertUnit);
			return ConvertUnitGroup(BWSAL::UnitGroup::getUnitGroup(unitsgroup));
		}
	}
}
